package br.com.sampleProject.java;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.servlet.ServletContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.com.softplan.pd.zion.faces.api.FacesUtils;

import com.uwyn.jhighlight.renderer.Renderer;
import com.uwyn.jhighlight.renderer.XhtmlRendererFactory;

public class HighlightUtils implements Serializable {

	private static final long serialVersionUID = -2377060338029020059L;

	private static final Logger LOG = LoggerFactory.getLogger(HighlightUtils.class);

	private static final String PATH_CODE_JAVA = "/WEB-INF/classes/";
	private static final String HIGHLIGHT_ENCODING = "windows-1252";
	private static final char SLASH = '/';
	private static final char BACKSLASH = '\\';
	private static final int CHAR_LENGTH = 1;
	private static final String END_OF_INPUT_DELIMITER = "\\Z";
	private static final String JAVA_EXTENSION = ".java";
	private static final char DOT = '.';
	private static final char LESS_SIGN = '<';
	private static final char GREATER_SIGN = '>';

	private static final String HTML_OPEN_NAV = "<nav id=\"tabsetDiv_nav\">";
	private static final String HTML_CLOSE_NAV = "</nav>";
	private static final String HTML_OPEN_UL = "<ul>";
	private static final String HTML_CLOSE_UL = "</ul>";
	private static final String HTML_OPEN_DIV_TABSET = "<div id=\"tabsetDiv_tabs\" class=\"z-tabs\">";
	private static final String HTML_CLOSE_DIV = "</div>";
	private static final String HTML_CLOSE_SECTION = "</section>";
	private static final String HTML_OPEN_DIV = "<div>";
	private static final String HTML_OPEN_BODY = "<body>";
	private static final String HTML_CLOSE_BODY = "</body>";
	private static final String HTML_OPEN_CODE = "<code>";
	private static final String HTML_OPEN_H1 = "<h1>";
	private static final String HTML_CLOSE_SECTION_WITH_CLASS_TAB = "\"class=\"z-tab\">";
	private static final String HTML_CLOSE_SECTION_WITH_CLASS_TAB_ACTIVE = "\" class=\"z-tab z-active\">";
	private static final String HTML_OPEN_SECTION_WITH_ID_TAB_INDEX = "<section id=\"_tab";
	private static final String HTML_TABSET_FOOTER = "<div class=\"z-clear\"></div><script>zion.ui.Tabset({id:\"tabsetDiv\",activeTab:\"_tab0\"});</script></div>";
	private static final String HTML_TABSET_HEADER = "<div id=\"tabsetDiv\" data-ziontype=\"tabset\" class=\"z-highlight\">" +
			"<input type=\"hidden\" name=\"tabsetDiv_activeTab\" id=\"tabsetDiv_activeTab\" value=\"0\" />" +
			"<div id=\"tabsetDiv_messages_wrapper\" class=\"z-messages-wrapper\"></div>";

	private static List<String> highlight;

	private HighlightUtils() {

	}

	/**
	 * Classe responsável por aplicar a formatação do código nos arquivos passados como parâmetro. Dado o nome do arquivo é feita a
	 * leitura do conteúdo do arquivo e aplicado os estilos de formatação e então o código formatado é armazenado em uma String.
	 *
	 * @param xhtml
	 * @param classes
	 * @return String
	 */
	public static String getPageHighlight(final String[] xhtml, final Class[] classes) {
		String htmlReturn = null;

		try {
			highlight = new ArrayList<String>();
			createHighlightXHTML(xhtml);
			createHighlightClass(classes);
			htmlReturn = builderHTMLFrameView();

		} catch (Exception e) {
			LOG.error("Error : " + e.getMessage());
		}
		return htmlReturn;
	}

	/**
	 *
	 * @param xhtml
	 * @throws Exception
	 */
	private static void createHighlightXHTML(final String[] xhtml) throws Exception {
		for (String codeView: xhtml) {
			highlight.add(transformsCodeToHighlight(codeView, XhtmlRendererFactory.XHTML));
		}
	}

	/**
	 *
	 * @param classes
	 * @throws Exception
	 */
	private static void createHighlightClass(final Class[] classes) throws Exception {
		for (Class codePOJO: classes) {
			highlight.add(transformsCodeToHighlight(codePOJO.getCanonicalName(), XhtmlRendererFactory.JAVA));
		}
	}

	/**
	 * Classe responsável por ler o arquivo e transformar e formatar o código desse arquivo para que seja criado o HTML
	 * de apresentação desse código.
	 *
	 * @param xhtml
	 * @param java
	 * @return
	 * @throws Exception
	 */
	private static String transformsCodeToHighlight(final String fileCode, final String type) throws Exception {
		Map<String, String> fileDetail = new HashMap<String, String>();
		try {
			fileDetail = getPathOfFileAndFileName(fileCode, type);

			//classe externa ao projeto que faz a compilação do código em html com os estilos formatados.
			Renderer r = XhtmlRendererFactory.getRenderer(type);
			String code = r.highlight(fileDetail.get("fileName"), new Scanner(new File(fileDetail.get("filePath"))).useDelimiter(END_OF_INPUT_DELIMITER).next(), HIGHLIGHT_ENCODING, false);

			return getSubstringWithoutHeadFooter(code);

		} catch (FileNotFoundException f) {
			return HTML_OPEN_H1 + fileDetail.get("fileName") + "</h1><code>Não foi possível encontrar o arquivo.</code>";
		} catch (IndexOutOfBoundsException i) {
			return HTML_OPEN_H1 + fileDetail.get("fileName") + "</h1><code>Erro ao tentar formatar o código do arquivo.</code>";
		}
	}

	/**
	 * como o objetivo é apresentar internamente em uma página é descartado os
	 * cabeçalhos html e pego apenas o conteúdo dentro das tags <body></body>.
	 * @param code
	 * @return
	 */
	private static String getSubstringWithoutHeadFooter(final String code) {
		if (code.contains(HTML_OPEN_BODY) && code.contains(HTML_CLOSE_BODY)) {
			return code.substring(code.indexOf(HTML_OPEN_BODY) + HTML_OPEN_BODY.length(), code.indexOf(HTML_CLOSE_BODY));
		} else {
			return "";
		}
	}

	/**
	 * Monta o caminho real e nome dos arquivo que será lido.
	 * @param fileCode
	 * @param type
	 * @return
	 */
	private static Map<String, String> getPathOfFileAndFileName(final String fileCode, final String type) throws FileNotFoundException {
		ServletContext servletContext = (ServletContext) FacesUtils.getExternalContext().getContext();
		Map<String, String> fileDetail = new HashMap<String, String>();
		String filePath = "";
		String fileName = "";

		if (!type.equals(XhtmlRendererFactory.XHTML)) {
			filePath = servletContext.getRealPath(PATH_CODE_JAVA.concat(fileCode.replace(DOT, SLASH).concat(JAVA_EXTENSION)));
			fileName = getFileNameOfPath(filePath, type);

		} else {
			filePath = servletContext.getRealPath(fileCode);
			fileName = getFileNameOfPath(filePath, type);
		}

		fileDetail.put("filePath", filePath);
		fileDetail.put("fileName", fileName);
		return fileDetail;
	}

	/**
	 * Dado o caminho "...\projec\samples\index.xhtml" retorna apenas o nome do arquivo com extenção "index.xhtml".
	 * @param filePath
	 * @param type
	 * @return
	 */
	private static String getFileNameOfPath(final String filePath, final String type) {
		return filePath.substring(filePath.lastIndexOf(BACKSLASH) + CHAR_LENGTH, filePath.length());
	}

	/**
	 * Responsável por criar um bloco de código HTML responsável pela apresentação do conteúdo dos arquivos fonte formatado.
	 * @return
	 */
	private static String builderHTMLFrameView() {
		StringBuffer sourceHTMLFrameSet = new StringBuffer();

		//criar cabeçalho tabset.
		sourceHTMLFrameSet.append(HTML_TABSET_HEADER);

		//criar abas de navegacao.
		sourceHTMLFrameSet.append(HTML_OPEN_NAV);
		sourceHTMLFrameSet.append(HTML_OPEN_UL);

		for (int i = 0; i < highlight.size(); i++) {
			String nameTab = highlight.get(i).substring(0, highlight.get(i).indexOf(HTML_OPEN_CODE));
			highlight.set(i, highlight.get(i).replace(nameTab, ""));
			//criar item da listagem
			sourceHTMLFrameSet.append(builderHTMLListItemElement(i, nameTab));
		}
		sourceHTMLFrameSet.append(HTML_CLOSE_UL);
		sourceHTMLFrameSet.append(HTML_CLOSE_NAV);

		//criar tabs.
		sourceHTMLFrameSet.append(HTML_OPEN_DIV_TABSET);
		for (int i = 0; i < highlight.size(); i++) {
			if (i == 0) {
				sourceHTMLFrameSet.append(HTML_OPEN_SECTION_WITH_ID_TAB_INDEX + i + HTML_CLOSE_SECTION_WITH_CLASS_TAB_ACTIVE);
			} else {
				sourceHTMLFrameSet.append(HTML_OPEN_SECTION_WITH_ID_TAB_INDEX + i + HTML_CLOSE_SECTION_WITH_CLASS_TAB);
			}
			sourceHTMLFrameSet.append(HTML_OPEN_DIV);
			sourceHTMLFrameSet.append(highlight.get(i));
			sourceHTMLFrameSet.append(HTML_CLOSE_DIV);
			sourceHTMLFrameSet.append(HTML_CLOSE_SECTION);
		}
		sourceHTMLFrameSet.append(HTML_CLOSE_DIV);

		//fechar tabset.
		sourceHTMLFrameSet.append(HTML_TABSET_FOOTER);

		return sourceHTMLFrameSet.toString();
	}

	private static Object builderHTMLListItemElement(final int itemIndex, final String nameTab) {
		if (itemIndex == 0) {
			return "<li id=\"_nav:" + itemIndex + "\" class=\"z-active\"><a href=\"#_tab" + itemIndex + "\">" + nameTab.subSequence(nameTab.indexOf(GREATER_SIGN) + CHAR_LENGTH, nameTab.lastIndexOf(LESS_SIGN)) + "</a></li>";
		} else {
			return "<li id=\"_nav:" + itemIndex + "\"><a href=\"#_tab" + itemIndex + "\">" + nameTab.subSequence(nameTab.indexOf(GREATER_SIGN) + CHAR_LENGTH, nameTab.lastIndexOf(LESS_SIGN)) + "</a></li>";
		}
	}

}
